package com.dlc.shop.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dlc.shop.bean.enums.*;
import com.dlc.shop.bean.model.*;
import com.dlc.shop.bean.param.TakeStockParam;
import com.dlc.shop.bean.vo.SkuStockVO;
import com.dlc.shop.bean.vo.TakeStockVO;
import com.dlc.shop.bean.vo.WarehouseVO;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.LuaOperateEnum;
import com.dlc.shop.common.constants.SegmentIdKey;
import com.dlc.shop.common.constants.StockPointType;
import com.dlc.shop.common.enums.SysTypeEnum;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.util.PageParam;
import com.dlc.shop.dao.StationMapper;
import com.dlc.shop.dao.TakeStockMapper;
import com.dlc.shop.manager.impl.LangManager;
import com.dlc.shop.service.*;
import com.dlc.shop.util.StockUtil;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Citrus
 * @date 2021-09-15 11:18:33
 */
@Service
@AllArgsConstructor
public class TakeStockServiceImpl extends ServiceImpl<TakeStockMapper, TakeStock> implements TakeStockService {

    private final StationMapper stationMapper;
    private final TakeStockMapper takeStockMapper;

    private final TakeStockProdService takeStockProdService;
    private final SkuService skuService;
    private final ProductService productService;
    private final WarehouseService warehouseService;
    private final SegmentService segmentService;
    private final SkuStockService skuStockService;
    private final StockBillLogService stockBillLogService;

    private final LangManager langManager;

    @Override
    public IPage<TakeStockVO> pageByParams(PageParam<TakeStock> page, TakeStockParam takeStock) {
        return takeStockMapper.pageByParams(page, takeStock);
    }

    @Override
    public TakeStockVO getInfo(Long takeStockId) {
        TakeStock takeStock = this.getById(takeStockId);
        TakeStockVO takeStockVO = new TakeStockVO();
        BeanUtils.copyProperties(takeStock, takeStockVO);
        // 补充仓库名称
        WarehouseVO warehouseVO = warehouseService.getByWarehouseId(takeStockVO.getWarehouseId());
        if (!Objects.isNull(warehouseVO)) {
            takeStockVO.setWarehouseName(warehouseVO.getWarehouseName());
        }
        return takeStockVO;
    }

    @Override
    public Long saveTakeStock(TakeStock takeStock) {
        if (Objects.isNull(takeStock.getCreateTime())) {
            //盘点开始时间不能为空
            throw new YamiShopBindException("yami.take.stock.time.not.null");
        }
        // 校验仓库
        WarehouseVO warehouseVO = this.checkWarehouse(takeStock.getWarehouseId(), takeStock.getShopId());
        takeStock.setStockPointType(Objects.equals(warehouseVO.getSysType(), SysTypeEnum.STATION.value()) ? StockPointType.STATION.getValue() : StockPointType.WAREHOUSE.getValue());
        takeStock.setTakeStockNo(StringUtils.join("TSC", String.valueOf(segmentService.getSegmentId(SegmentIdKey.STOCK))));
        takeStock.setBillStatus(TakeStockBillStatusEnum.TAKING.value());
        this.save(takeStock);
        return takeStock.getTakeStockId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateTakeStock(TakeStock takeStock) {
        TakeStock dbTakeStock = this.checkInfo(takeStock, false);
        updateById(takeStock);
        //更新账面库存
        List<TakeStockProd> changeProdList = new ArrayList<>();
        List<TakeStockProd> takeStockProdList = takeStock.getTakeStockProdList();
        List<Long> deleteIds = this.updateBookStock(takeStockProdList, changeProdList, dbTakeStock);
        if (CollectionUtils.isNotEmpty(deleteIds)) {
            takeStockProdService.removeByIds(deleteIds);
        }
        return takeStockProdService.saveOrUpdateBatch(changeProdList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean finishTakeStock(TakeStock takeStock) {
        TakeStock dbTakeStock = this.checkInfo(takeStock, false);
        takeStock.setMaker(dbTakeStock.getMaker());
        takeStock.setBillStatus(TakeStockBillStatusEnum.COMPLETED.value());
        updateById(takeStock);
        List<TakeStockProd> takeStockProdList = takeStock.getTakeStockProdList();
        List<TakeStockProd> changeProdList = new ArrayList<>();
        List<Long> deleteIds = this.updateBookStock(takeStockProdList, changeProdList, dbTakeStock);
        if (CollectionUtils.isNotEmpty(deleteIds)) {
            takeStockProdService.removeByIds(deleteIds);
        }
        boolean updateBatch = takeStockProdService.saveOrUpdateBatch(changeProdList);
        // 记录出入库信息
        Map<Integer, List<Sku>> successTakeStockMap = new HashMap<>(Constant.INITIAL_CAPACITY);
        this.completedCheck(changeProdList, takeStock.getWarehouseId(), successTakeStockMap);
        // 盘盈记录
        StockBillLog stockBillLog;
        List<SkuStockVO> skuStockVOList = new ArrayList<>(changeProdList.size());
        List<Sku> changeProds = successTakeStockMap.get(TakeStockProdStatusEnum.PROFIT.value());
        if (!CollectionUtils.isEmpty(changeProds)) {
            stockBillLog = this.buildStockBillLog(takeStock, StockBillType.PROFIT_STORAGE, changeProds);
            stockBillLogService.addStockBillLog(stockBillLog);
            for (Sku sku : changeProds) {
                skuStockVOList.add(this.buildSkuStock(sku, dbTakeStock.getWarehouseId(), LuaOperateEnum.SKU_ADD));
            }
        }
        // 盘亏记录
        changeProds = successTakeStockMap.get(TakeStockProdStatusEnum.LOSS.value());
        if (!CollectionUtils.isEmpty(changeProds)) {
            stockBillLog = this.buildStockBillLog(takeStock, StockBillType.LOSS_OUTBOUND, changeProds);
            stockBillLogService.addStockBillLog(stockBillLog);
            for (Sku sku : changeProds) {
                skuStockVOList.add(this.buildSkuStock(sku, dbTakeStock.getWarehouseId(), LuaOperateEnum.SKU_SUB));
            }
        }
        // 修改库存
        skuStockService.changeSkuStock(skuStockVOList);
        return updateBatch;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean voidedTakeStock(TakeStock takeStock) {
        TakeStock dbTakeStock = this.checkInfo(takeStock, true);
        takeStock.setBillStatus(TakeStockBillStatusEnum.VOIDED.value());
        updateById(takeStock);
        List<TakeStockProd> takeStockProdList = takeStock.getTakeStockProdList();
        ArrayList<TakeStockProd> changeProdList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(takeStockProdList)) {
            List<Long> deleteIds = this.updateBookStock(takeStockProdList, changeProdList, dbTakeStock);
            if (CollectionUtils.isNotEmpty(deleteIds)) {
                takeStockProdService.removeByIds(deleteIds);
            }
        }
        if (CollectionUtils.isEmpty(changeProdList)) {
            return true;
        } else {
            return takeStockProdService.saveOrUpdateBatch(changeProdList);
        }
    }

    @Override
    public List<TakeStockVO> listByParams(TakeStockParam takeStock) {
        List<TakeStockVO> takeStockList = takeStockMapper.listByParams(takeStock);
        langManager.getTakeStockVoLang(takeStockList);
        return takeStockList;
    }

    @Override
    public TakeStockVO getTakeStock(Long takeStockId) {
        return takeStockMapper.getTakeStockById(takeStockId);
    }

    @Override
    public Long countUnFinish(Long warehouseId) {
        if (Objects.isNull(warehouseId)) {
            return 0L;
        }
        return this.count(new LambdaQueryWrapper<TakeStock>()
                .eq(TakeStock::getWarehouseId, warehouseId)
                .eq(TakeStock::getBillStatus, TakeStockBillStatusEnum.TAKING.value()));
    }

    @Override
    public void nullifyByWarehouseId(Long warehouseId) {
        // 查找未完成/作废的实物盘点
        List<TakeStock> takeStocks = this.list(new LambdaQueryWrapper<TakeStock>()
                .eq(TakeStock::getWarehouseId, warehouseId)
                .eq(TakeStock::getBillStatus, TakeStockBillStatusEnum.TAKING.value()));
        if (CollectionUtils.isEmpty(takeStocks)) {
            return;
        }
        // 作废（直接更新状态）
        for (TakeStock takeStock : takeStocks) {
            takeStock.setBillStatus(TakeStockBillStatusEnum.VOIDED.value());
        }
        this.updateBatchById(takeStocks);
    }

    private void completedCheck(List<TakeStockProd> changeProdList, Long warehouseId, Map<Integer, List<Sku>> successTakeStockMap) {
        List<Long> skuIds = changeProdList.stream().map(TakeStockProd::getSkuId).collect(Collectors.toList());
        List<Sku> skus = skuService.listSkuBySkuIds(skuIds);
        // 补充库存
        List<SkuStockVO> skuStocks = skuIds.stream().map(skuId -> new SkuStockVO(skuId, warehouseId)).collect(Collectors.toList());
        Map<String, SkuStockVO> skuStockMap = skuStockService.listSkuStock(skuStocks);
        SkuStockVO skuStockVO;
        for (Sku sku : skus) {
            skuStockVO = skuStockMap.get(StockUtil.skuPointKey(sku.getSkuId(), warehouseId));
            sku.setStocks(Objects.isNull(skuStockVO) ? 0 : skuStockVO.getStock());
        }
        if (CollectionUtils.isEmpty(skus)) {
            //商品规格不存在
            throw new YamiShopBindException("yami.shop.sku.is.null");
        }
        Map<Long, Sku> skuMap = skus.stream().collect(Collectors.toMap(Sku::getSkuId, s -> s));
        // 用于记录盘亏的商品信息
        List<Sku> lossProdList = new ArrayList<>();
        // 用于记录盘盈的商品信息
        List<Sku> profitProdList = new ArrayList<>();
        Sku sku;
        for (TakeStockProd prod : changeProdList) {
            sku = skuMap.get(prod.getSkuId());
            //盈亏以及异常判断
            if (Objects.isNull(sku) || sku.getIsDelete() == 1) {
                //sku删除，异常
                prod.setIoType(TakeStockProdStatusEnum.EXCEPTION.value());
                prod.setExceptionReason(TakeStockExceptionEnum.DELETE_PROD.value());
                continue;
            } else if (!prod.getStocks().equals(sku.getStocks())) {
                //库存不相等，异常
                prod.setIoType(TakeStockProdStatusEnum.EXCEPTION.value());
                prod.setExceptionReason(TakeStockExceptionEnum.PROD_CHANGE.value());
                continue;
            }
            if (prod.getStocks() > prod.getTotalStock()) {
                //盘亏
                prod.setIoType(TakeStockProdStatusEnum.LOSS.value());
                updateStock(prod, sku);
                // 保存商品信息用于记录出库
                lossProdList.add(sku);
            } else if (prod.getStocks() < prod.getTotalStock()) {
                //盘盈
                prod.setIoType(TakeStockProdStatusEnum.PROFIT.value());
                updateStock(prod, sku);
                // 保存商品信息用于记录入库
                profitProdList.add(sku);
            } else if (prod.getStocks().equals(prod.getTotalStock())) {
                //盘平
                prod.setIoType(TakeStockProdStatusEnum.EQUAL.value());
            }
            productService.removeProdCacheByProdId(prod.getProdId());
            skuService.removeSkuCacheBySkuId(prod.getSkuId(), prod.getProdId());
        }
        successTakeStockMap.put(TakeStockProdStatusEnum.LOSS.value(), lossProdList);
        successTakeStockMap.put(TakeStockProdStatusEnum.PROFIT.value(), profitProdList);
    }

    private void updateStock(TakeStockProd prod, Sku sku) {
        sku.setChangeStock(prod.getTotalStock() - prod.getStocks());
        sku.setStocks(prod.getTotalStock());
    }

    private TakeStock checkInfo(TakeStock takeStock, boolean isVoid) {
        TakeStock dbTakeStock = takeStockMapper.selectById(takeStock.getTakeStockId());
        if (!Objects.equals(dbTakeStock.getShopId(), takeStock.getShopId())) {
            //当前盘点信息不属于你的店铺
            throw new YamiShopBindException("yami.take.stock.message.error");
        }
        if (Objects.equals(dbTakeStock.getBillStatus(), TakeStockBillStatusEnum.COMPLETED.value())
                || Objects.equals(dbTakeStock.getBillStatus(), TakeStockBillStatusEnum.VOIDED.value())) {
            //当前盘点任务已完成或已作废，请刷新页面
            throw new YamiShopBindException("yami.take.stock.status.error");
        }
        //不是废弃再进行判断
        if (!isVoid) {
            List<TakeStockProd> prods = takeStock.getTakeStockProdList();
            for (TakeStockProd prod : prods) {
                if (Objects.isNull(prod.getTotalStock())) {
                    //商品实盘库存不能为空
                    throw new YamiShopBindException("yami.shop.total.stock.not.null");
                }
            }
        }
        return dbTakeStock;
    }

    /**
     * 更新账面库存，删除商品
     */
    private List<Long> updateBookStock(List<TakeStockProd> takeStockProdList, List<TakeStockProd> changeProdList, TakeStock takeStock) {
        List<TakeStockProd> dbProdList = takeStockProdService.list(new LambdaQueryWrapper<TakeStockProd>()
                .eq(TakeStockProd::getTakeStockId, takeStock.getTakeStockId()));
        List<Long> dbProdIds = dbProdList.stream().map(TakeStockProd::getTakeStockProdId).collect(Collectors.toList());

        List<Long> skuIds = takeStockProdList.stream().map(TakeStockProd::getSkuId).collect(Collectors.toList());
        Map<Long, Sku> skuMap = skuService.listSkuBySkuIds(skuIds).stream().collect(Collectors.toMap(Sku::getSkuId, s -> s));
        // 补充库存
        List<SkuStockVO> skuStocks = skuIds.stream().map(skuId -> new SkuStockVO(skuId, takeStock.getWarehouseId())).collect(Collectors.toList());
        Map<String, SkuStockVO> skuStockMap = skuStockService.listSkuStock(skuStocks);
        SkuStockVO skuStockVO;
        for (Sku sku : skuMap.values()) {
            skuStockVO = skuStockMap.get(StockUtil.skuPointKey(sku.getSkuId(), takeStock.getWarehouseId()));
            sku.setStocks(Objects.isNull(skuStockVO) ? 0 : skuStockVO.getStock());
        }
        List<Long> deleteIds = new ArrayList<>();
        for (TakeStockProd prod : takeStockProdList) {
            if (!skuMap.containsKey(prod.getSkuId())) {
                continue;
            }
            //普通商品
            Sku sku = skuMap.get(prod.getSkuId());

            if (prod.getStocks() > prod.getTotalStock()) {
                //盘亏
                prod.setIoType(TakeStockProdStatusEnum.LOSS.value());
            } else if (prod.getStocks() < prod.getTotalStock()) {
                //盘盈
                prod.setIoType(TakeStockProdStatusEnum.PROFIT.value());
            } else if (prod.getStocks().equals(prod.getTotalStock())) {
                //盘平
                prod.setIoType(TakeStockProdStatusEnum.EQUAL.value());
            }
            if (prod.getTakeStockProdId() != null && dbProdIds.contains(prod.getTakeStockProdId())) {
                prod.setStocks(sku.getStocks());
                changeProdList.add(prod);
                deleteIds.add(prod.getTakeStockProdId());
                continue;
            }
            //新增且sku存在
            prod.setStocks(sku.getStocks());
            changeProdList.add(prod);
        }
        dbProdIds.removeAll(deleteIds);
        return dbProdIds;
    }

    /**
     * 检查仓库
     * @param warehouseId 仓库id
     * @param shopId 店铺id
     */
    private WarehouseVO checkWarehouse(Long warehouseId,
                                       Long shopId) {
        WarehouseVO warehouse = warehouseService.getByWarehouseId(warehouseId);
        if (Objects.isNull(warehouse)) {
            throw new YamiShopBindException(String.format("仓库[%s]不存在", warehouseId));
        }
        if (Objects.equals(warehouse.getSysType(), SysTypeEnum.STATION.value())) {
            Station station = stationMapper.getStationInfoById(warehouse.getShopId());
            if (!Objects.equals(station.getShopId(), shopId))  {
                throw new YamiShopBindException(String.format("门店[%s]非该店铺的门店", station.getStationId()));
            }
        } else if (!Objects.equals(shopId, warehouse.getShopId())) {
            throw new YamiShopBindException(String.format("仓库[%s]非该店铺的仓库", warehouseId));
        }
        return warehouse;
    }

    /**
     * 构建库存流水日志类
     * @param takeStock 实物盘点
     * @param stockBillType 库存流水类型
     * @return 库存流水日志
     */
    private StockBillLog buildStockBillLog(TakeStock takeStock, StockBillType stockBillType, List<Sku> changeProds) {
        StockBillLog stockBillLog = new StockBillLog();
        stockBillLog.setShopId(takeStock.getShopId());
        stockBillLog.setEmployeeId(takeStock.getMaker());
        stockBillLog.setMakerMobile(takeStock.getMakerMobile());
        stockBillLog.setStockBillType(stockBillType.value());
        stockBillLog.setType(Objects.equals(stockBillType, StockBillType.PROFIT_STORAGE) ? StockType.WAREHOUSING.value() : StockType.OUT_OF_STOCK.value());
        stockBillLog.setSourceOrderNo(takeStock.getTakeStockNo());
        stockBillLog.setStockPointId(takeStock.getWarehouseId());
        stockBillLog.setStockPointType(takeStock.getStockPointType());
        stockBillLog.setStatus(StockBillStatus.SUCCESS.value());
        stockBillLog.setRemark(takeStock.getRemark());
        stockBillLog.setBusinessTime(new Date());
        // 构建库存流水项
        int totalCount = 0;
        double totalAmount = 0.0;
        StockBillLogItem stockBillLogItem;
        List<StockBillLogItem> stockBillLogItems = new ArrayList<>(changeProds.size());
        for (Sku sku : changeProds) {
            stockBillLogItem = this.buildStockBillLogItem(sku);
            stockBillLogItems.add(stockBillLogItem);
            totalCount += stockBillLogItem.getStockCount();
            totalAmount += stockBillLogItem.getStockCount() + stockBillLogItem.getUnitPrice();
        }
        stockBillLog.setTotalCount(totalCount);
        stockBillLog.setTotalAmount(totalAmount);
        stockBillLog.setStockBillLogItems(stockBillLogItems);
        return stockBillLog;
    }

    /**
     * 构建库存流水项
     * @param sku sku
     * @return 库存流水项
     */
    private StockBillLogItem buildStockBillLogItem(Sku sku) {
        StockBillLogItem stockBillLogItem = new StockBillLogItem();
        stockBillLogItem.setStockCount(Math.abs(sku.getChangeStock()));
        stockBillLogItem.setAfterStock(sku.getStocks() + sku.getChangeStock());
        stockBillLogItem.setProdId(sku.getProdId());
        stockBillLogItem.setSkuId(sku.getSkuId());
        stockBillLogItem.setUnitPrice(sku.getPrice());
        return stockBillLogItem;
    }

    /**
     * 构建sku库存对象
     * @param sku sku
     * @param warehouseId 仓库id
     * @param luaOperateEnum 库存操作
     * @return sku库存对象
     */
    private SkuStockVO buildSkuStock(Sku sku, Long warehouseId, LuaOperateEnum luaOperateEnum) {
        SkuStockVO skuStockVO = new SkuStockVO();
        BeanUtils.copyProperties(sku, skuStockVO);
        skuStockVO.setProdId(sku.getProdId());
        skuStockVO.setStockPointId(warehouseId);
        skuStockVO.setOperateType(luaOperateEnum.value());
        skuStockVO.setStock(Math.abs(sku.getChangeStock()));
        return skuStockVO;
    }
}
